﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GeckoPhotos.Repository;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Data;

namespace GeckoPhotos.Tests.TestUtil
{

    /// <summary>
    /// Simple in-memory repository.  Note that returned objects are the private 
    /// objects stored internally, so modifications to the returned objects are
    /// "instant", making calls to Update() redundant.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TModel"></typeparam>
    public class InMemoryRepository<TKey, TModel> : IRepository<TKey, TModel>
        where TKey : IComparable
        where TModel : EntityObject
    {
        protected readonly List<TModel> _items = new List<TModel>();

        /// <summary>
        /// Returns the key for the object specified.  Uses reflection, so quite 
        /// costly for what would normally be a simple read.  Note this method
        /// only checks the value of the first [EdmScalarPropertyAttribute(EntityKeyProperty=true)]
        /// property it finds, so would be unsuitable for multi-key entities (which
        /// aren't supported by IRepository anyway).
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        internal static TKey GetKey(TModel o)
        {
            System.Reflection.PropertyInfo[] properties = o.GetType().GetProperties();

            foreach (System.Reflection.PropertyInfo propInfo in properties)
            {
                var attributes = propInfo.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
                if (attributes.Length == 1)
                {
                    var attribute = (EdmScalarPropertyAttribute)attributes[0];
                    if (attribute.EntityKeyProperty)
                    {
                        // We have found the property flagged with EntityKeyProperty==true
                        return (TKey)propInfo.GetValue(o, null);
                    }
                }
            }

            throw new ArgumentException("Type of passed object did not have a [EdmScalarPropertyAttribute(EntityKeyProperty=true)] property defined.");
        }

        internal static bool EqualsByKey(TModel x, TModel y)
        {
            return GetKey(x).CompareTo(GetKey(y)) == 0;
        }

        IQueryable<TModel> GeckoPhotos.Repository.IRepository<TKey, TModel>.Where(Func<TModel, bool> query)
        {
            return (from i in _items
                    where query(i)
                    select i).AsQueryable();
        }

        TModel GeckoPhotos.Repository.IRepository<TKey, TModel>.Single(Func<TModel, bool> query)
        {
            return (from i in _items
                    where query(i)
                    select i).Single();
        }

        IQueryable<TModel> GeckoPhotos.Repository.IRepository<TKey, TModel>.All()
        {
            return _items.AsQueryable();
        }

        void GeckoPhotos.Repository.IRepository<TKey, TModel>.Insert(TModel entity)
        {
            if (_items.Contains(entity, EqualityComparerByKey.Instance))
            {
                throw new InvalidOperationException("Key already exists.");
            }
            else
            {
                _items.Add(entity);
            }
        }

        void GeckoPhotos.Repository.IRepository<TKey, TModel>.Update(TModel entity)
        {
            // Do nothing - in-memory object would already be updated
        }

        void GeckoPhotos.Repository.IRepository<TKey, TModel>.Remove(TModel entity)
        {
            _items.RemoveAll(i => EqualsByKey(entity, i));
        }

        private class EqualityComparerByKey : IEqualityComparer<TModel>
        {
            private static readonly EqualityComparerByKey _instance = new EqualityComparerByKey();

            private EqualityComparerByKey()
            {
            }

            public static EqualityComparerByKey Instance
            {
                get { return _instance; }
            }

            bool IEqualityComparer<TModel>.Equals(TModel x, TModel y)
            {
                return EqualsByKey(x, y);
            }

            int IEqualityComparer<TModel>.GetHashCode(TModel obj)
            {
                return GetKey(obj).GetHashCode();
            }
        }

    }

    public class InMemoryRepository<TModel> : InMemoryRepository<int, TModel>, IRepository<int, TModel>
        where TModel : EntityObject
    {
    }

}
